React to the Raspberry Pi GPIO pins

This IPython Notebook teaches us to connect hardware inputs to software. We will connect a button to the General Purpose Input/Output (GPIO) pins* on the Raspberry Pi and when the button is pushed, make the Pi run a function.

The GPIO pins are the 40 (numbered) pins that electronic components can be connected to.

But let's first make a function that the Pi can execute. There are a few sounds installed on the Pi in the form of .wav files (something similar to .mp3 files) and there is a library on the Pi that can play these sounds (pygame; a library used to develop games in Python)

So, we will import the mixer module from the pygame library and initialise it:

IPython Instructions:

Place your cursor in the cell below and press Shift+Enter or click the Play button in the toolbar above to execute the code in the cell.

  • Shift + Enter: Execute the cell and jump to the next one
  • Ctrl + Enter: Execute the cell, but stay in the current one
  • Alt + Enter: Execute the cell and create a new one below the current one

As long as a [*] appears to the left os the cell, it is still running. As soon as the code ends, a number appears and any output generated by the code is printed under the cell.


In [ ]:
import pygame.mixer
pygame.mixer.init()

We will now ask the pygame library to load a .wav file into memory and make it into a sound, ready to play.
(and because we can't control ourselves, we play it once; do not forget to attach boxes or headphones)

note: we get the .wav file from a folder that is installed as part of the Sonic Pi software


In [ ]:
drum = pygame.mixer.Sound("/opt/sonic-pi/etc/samples/drum_tom_mid_hard.wav")
drum.play()

The play() method of the drum sound can be put in a selfmade function, so we can add stuff and reuse it later.


In [ ]:
def play():
    print("Drums !")
    drum.play()

Let's run our new play() function


In [ ]:
play()

Time to connect a button I would say. We'll use the BCM numbering, the way they are engraved on the case we're using and on the GPIO pinout document.

Use the following illustration as a guide:


In [ ]:
#load GPIO library
import RPi.GPIO as GPIO
#Set BCM (Broadcom) mode for the pin numbering
GPIO.setmode(GPIO.BCM)

#activate pin 17 as input and set the built-in pull-up/pull-down resistor to pull-up
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)

After this preparation, we can ask Python to "register an event".

We want our function to be called each time a certain event occurs, namely a "FALLING" event on pin 17. Because this (the voltage dropping on pin 17) means the button has been pressed. This can be done by attaching the function to the event with the GPIO.add_event_detect() function.

We will pass 4 parameters to the add_event_detect() function: 17: the pin number to listen on GPIO.FALLING: the kind of event to react to play: the function to call when the event is detected 200: the number of milliseconds to wait after an event detection before starting to listen again

There is, however, one change we have to make to our play() function.
The add_event_detect function wants to tell the play() function which pin the event came from, wo we need to add that as a parameter to the play() function, even if we don't use it (yet):


In [ ]:
def play(pin_number):
    print("Drum roll !")
    drum.play()

GPIO.add_event_detect(17, GPIO.FALLING, play, 200)

You can test it out by pressing the button.

If you're happy with the result, you can remove the event registration again with the following function:


In [ ]:
GPIO.remove_event_detect(17)

Ok, this was so much fun, let's do another one:


In [ ]:
cymbal = pygame.mixer.Sound("/opt/sonic-pi/etc/samples/drum_cymbal_open.wav")
cymbal.play()

This may be a good moment to show how to make our function more generic and reusable.
We'll store the sounds in a dictionary, where the pin number is the key and the sound is the value linked to that key:


In [ ]:
sound_pins = {
    17: drum,
    27: cymbal,
}

So we can now use (read: play) the sound object by pointing to the dictionary element with the pin number as the key.
Let's try it out?


In [ ]:
sound_pins[17].play()

Which means that, instead of making two seperate functions that we register one by one, we can now create one funcction that uses the pin number as input to play the appropriate tune:


In [ ]:
def play(pin):
    sound = sound_pins[pin]
    print("Geluid spelen voor pin %s" % pin)
    sound.play()

Play the cymbal:


In [ ]:
play(27)

And the drums:


In [ ]:
play(17)

Let's build the schema below to get our first, very rudimentary, music box:

Thanks to our more generic function, we can now "automate" the pin setup and the registration of events with the appropriate buttons and sounds by looping over the keys in the dictionary.


In [ ]:
for pin in sound_pins:
    GPIO.setup(pin, GPIO.IN, GPIO.PUD_UP)
    GPIO.add_event_detect(pin, GPIO.FALLING, play, 200)

Music time!

try it out...

Removing the registrations again happens in much the same way of course.


In [ ]:
for pin in sound_pins:
    GPIO.remove_event_detect(pin)

In [ ]:
#clean up the GPIO settings again
GPIO.cleanup()